using System;
using System.Collections.Generic;
using System.Text;
using System.IO.Compression;
using System.IO;

namespace RetailAnalytic.Core.ZipFile
{
    public class Xip_ClassFile
    {
        public static void NewArchiveARH(string FileName, Dictionary<string, byte[]> _Files)
        {
            if (File.Exists(FileName))
            {
                File.Delete(FileName);
            }

            string nameFileZIP = FileName;
            System.IO.FileMode mode = System.IO.FileMode.CreateNew;
            ZipFile archive = new ZipFile(nameFileZIP, mode);
            System.Collections.IEnumerator IE = _Files.Keys.GetEnumerator();
            while (IE.MoveNext())
            {
                string _name = IE.Current.ToString();
                if (System.IO.File.Exists(_name))
                {
                    archive.Add(_name);
                }
            }
            archive.Close();
            IE.Reset();
            while (IE.MoveNext())
            {
                string _name = IE.Current.ToString();
                FileInfo f = new FileInfo(_name);

                if (f.Extension != ".xip")
                {
                    File.Delete(_name);
                }
            }

        }
        public static void OpenArhiF(string FileName)
        {
            string name = FileName;
            System.IO.FileInfo fl = new System.IO.FileInfo(FileName);
            string DirName = fl.DirectoryName;
            ZipFile archive = new ZipFile(name);
            archive.ExtractAll(DirName);
            archive.Close();
            
        }
    }
    public class ZipFile
    {
        List<ZipEntry> zipEntries;
        private ZipReader thisReader;
        private ZipWriter thisWriter;
        private Stream baseStream;
        private string zipName;

        public ZipFile(string name, FileMode mode)
        {
            try
            {
                zipName = name;
                
                baseStream = new FileStream(zipName, mode);
                thisWriter = new ZipWriter(baseStream);
                thisWriter.Method = 6;
                thisWriter.WriteSuperHeader(0, 6);
                FileInfo FlI = new FileInfo(zipName);
                string ShortName = FlI.Name.Replace(FlI.Extension, "");
                thisReader = new ZipReader(baseStream, "\\"+ShortName);
                zipEntries = thisReader.GetAllEntries();
            }
            catch (Exception er)
            {
                Dialog.Show(Dialog_type.Error,SR.StringLibrary.GetStrMSG(SR.StringLibrary.KeyEnum.MESSAGE_EMPTY,
                    Environment.StackTrace, er));
            }
        }
        public ZipFile(string name)
        {
            try
            {
                zipName = name;
                baseStream = new FileStream(zipName, FileMode.Open);
                thisWriter = new ZipWriter(baseStream);
                FileInfo FlI = new FileInfo(zipName);
                string ShortName = FlI.Name.Replace(FlI.Extension, "");
                thisReader = new ZipReader(baseStream, "\\" + ShortName);
                zipEntries = thisReader.GetAllEntries();
                thisWriter.Method = thisReader.Method;
            }
            catch (Exception er)
            {
                Dialog.Show(Dialog_type.Error, SR.StringLibrary.GetStrMSG(SR.StringLibrary.KeyEnum.MESSAGE_EMPTY,
                    Environment.StackTrace, er));
            }
        }
        public void Add(string fileName)
        {
            System.Globalization.CultureInfo ci = System.Threading.Thread.CurrentThread.CurrentUICulture;
            ZipEntry entry = new ZipEntry(fileName);
            thisWriter.Add(entry);
            zipEntries.Add(entry);
            thisWriter.CloseHeaders((Int16)zipEntries.Count);
        }
        public void ExtractAll(string path)
        {
            thisReader.ExtractAll(zipEntries, path);
        }
        public void Close()
        {
            if (baseStream != null)
            {
                baseStream.Close();
            }
        }
    }
    public class ZipReader
    {
        private Stream zipStream;
        private string zipName;
        private Stream baseStream;
        private Int16 numberOfFiles;
        private byte method;

        System.Security.Cryptography.MD5CryptoServiceProvider md5;
        public ZipReader(Stream fileStream, string name)
        {
            zipName = name;
            baseStream = fileStream;
            numberOfFiles = -1;
            method = 255;
            md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
        }
        private void ReadSuperHeader()
        {
            numberOfFiles = ReadLeInt16();
            method = ReadLeByte();
            //if (method != ZipConstants.DEFLATE &&
            //    method != ZipConstants.GZIP)
            //    throw new ArgumentOutOfRangeException();
        }
        private int ReadBuf(byte[] outBuf, int length)
        {
            return baseStream.Read(outBuf, 0, length);
        }
        private byte ReadLeByte()
        {
            return (byte)baseStream.ReadByte();
        }
        private Int16 ReadLeInt16()
        {
            return (Int16)(ReadLeByte() | (ReadLeByte() << 8));
        }
        private Int32 ReadLeInt32()
        {
            return (UInt16)ReadLeInt16() | ((UInt16)ReadLeInt16() << 16);
        }
        private string ConvertToString(byte[] data)
        {
            return System.Text.Encoding.ASCII.GetString(data, 0, data.Length);
        }
        private ZipEntry GetNextEntry()
        {
            ZipEntry currentEntry = null;
            try
            {
                Int32 size = ReadLeInt32();
                if (size == -1)
                    return new ZipEntry(String.Empty);

                Int32 csize = ReadLeInt32();
                byte[] crc = new byte[16];
                ReadBuf(crc, crc.Length);

                Int32 dostime = ReadLeInt32();
                Int16 nameLength = ReadLeInt16();

                byte[] buffer = new byte[nameLength];
                ReadBuf(buffer, nameLength);
                string name = ConvertToString(buffer);

                currentEntry = new ZipEntry(name);
                currentEntry.Size = size;
                currentEntry.CompressedSize = csize;
                currentEntry.SetCrc(crc);
                currentEntry.DosTime = dostime;
            }
            catch (Exception er)
            {
                Dialog.Show(Dialog_type.Error, SR.StringLibrary.GetStrMSG(SR.StringLibrary.KeyEnum.MESSAGE_EMPTY,
                    Environment.StackTrace, er));
            }
            return currentEntry;
        }
        private void WriteUncompressedFile(ZipEntry entry, string completePath)
        {
            MemoryStream ms = new MemoryStream();
            try
            {
                byte[] b = new byte[entry.CompressedSize];
                baseStream.Read(b, 0, (int)entry.CompressedSize);
                if (CheckCRC(entry.GetCrc(), b))
                    ms.Write(b, 0, b.Length);
                ms.Seek(0, SeekOrigin.Begin);
                if (method == ZipConstants.DEFLATE)
                    zipStream = new DeflateStream(ms, CompressionMode.Decompress, false);
                else if (method == ZipConstants.GZIP)
                    zipStream = new GZipStream(ms, CompressionMode.Decompress, false);

                int index = entry.Name.LastIndexOf(ZipConstants.BackSlash);
                string name = completePath + entry.Name.Substring(index + 1);
                FileStream rewrite = new FileStream(name, FileMode.Create);
                b = new byte[entry.Size];
                zipStream.Read(b, 0, (int)entry.Size);

                rewrite.Write(b, 0, (int)entry.Size);
                rewrite.Close();
            }
            catch (Exception er)
            {
                Dialog.Show(Dialog_type.Error, SR.StringLibrary.GetStrMSG(SR.StringLibrary.KeyEnum.MESSAGE_EMPTY,
                    Environment.StackTrace, er));
            }
            finally
            {
                zipStream.Close();
                ms.Close();
            }
        }
        public void ExtractAll(List<ZipEntry> zipEntries, string startPath)
        {
            try
            {
                int jump = 3;
                baseStream.Seek(jump, SeekOrigin.Begin);
                foreach (ZipEntry entry in zipEntries)
                {
                    jump = ZipConstants.FixedHeaderSize + entry.NameLength;
                    baseStream.Seek(jump, SeekOrigin.Current);
                    WriteUncompressedFile(entry, startPath + ZipConstants.BackSlash);
                }
            }
            catch (Exception er)
            {
                Dialog.Show(Dialog_type.Error, SR.StringLibrary.GetStrMSG(SR.StringLibrary.KeyEnum.MESSAGE_EMPTY,
                    Environment.StackTrace, er));
            }
            finally
            {
                baseStream.Close();
                zipStream.Close();
            }
        }
        public List<ZipEntry> GetAllEntries()
        {
            List<ZipEntry> headers = null;
            try
            {
                if (method == 255 || numberOfFiles == -1)
                {
                    baseStream.Seek(0, SeekOrigin.Begin);
                    ReadSuperHeader();
                }
                headers = new List<ZipEntry>(numberOfFiles);
                baseStream.Seek(3, SeekOrigin.Begin);
                for (int i = 0; i < numberOfFiles; ++i)
                {
                    ZipEntry entry = GetNextEntry();
                    headers.Add(entry);
                    baseStream.Seek(entry.CompressedSize, SeekOrigin.Current);
                }
            }
            catch (Exception er)
            {
                Dialog.Show(Dialog_type.Error, SR.StringLibrary.GetStrMSG(SR.StringLibrary.KeyEnum.MESSAGE_EMPTY,
                    Environment.StackTrace, er));
            }

            return headers;
        }
        public byte Method
        {
            get
            {
                return method;
            }
        }
        private bool CheckCRC(byte[] crc, byte[] data)
        {
            byte[] newCrc = md5.ComputeHash(data);
            for (int i = 0; i < crc.Length; ++i)
            {
                if (crc[i] != newCrc[i])
                    return false;
            }
            return true;
        }
    }
    public class ZipWriter
    {
        private Stream zipStream;
        private Stream baseStream;
        private long offset;
        private byte method;
        System.Security.Cryptography.MD5CryptoServiceProvider md5;

        public ZipWriter(Stream fileStream)
        {
            baseStream = fileStream;
            md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
        }
        public void WriteSuperHeader(Int16 number, byte mode)
        {
            baseStream.Seek(0, SeekOrigin.Begin);
            WriteLeInt16(number);
            baseStream.WriteByte(mode);
        }
        private void WriteSuperHeader(Int16 number)
        {
            try
            {
                long pos = baseStream.Position;
                baseStream.Seek(0, SeekOrigin.Begin);
                WriteLeInt16(number);
                baseStream.Seek(pos, SeekOrigin.Begin);
            }
            catch (Exception er)
            {
                Dialog.Show(Dialog_type.Error, SR.StringLibrary.GetStrMSG(SR.StringLibrary.KeyEnum.MESSAGE_EMPTY,
                    Environment.StackTrace, er));
            }
        }
        private void WriteBytes(byte[] value)
        {
            foreach (byte b in value)
                baseStream.WriteByte(b);
        }
        private void WriteLeInt16(Int16 value)
        {
            baseStream.WriteByte((byte)value);
            baseStream.WriteByte((byte)(value >> 8));
        }
        private void WriteLeInt32(Int32 value)
        {
            WriteLeInt16((Int16)value);
            WriteLeInt16((Int16)(value >> 16));
        }
        private void PutNextHeader(ZipEntry entry)
        {
            try
            {
                WriteLeInt32(entry.Size);
                //REcord the offset to write proper CRC and compressed size
                offset = baseStream.Position;
                WriteLeInt32(entry.CompressedSize);
                WriteBytes(entry.GetCrc());
                WriteLeInt32(entry.DosTime);
                WriteLeInt16(entry.NameLength);
                byte[] names = ConvertToArray(entry.Name);
                baseStream.Write(names, 0, names.Length);
            }
            catch (Exception er)
            {
                Dialog.Show(Dialog_type.Error, SR.StringLibrary.GetStrMSG(SR.StringLibrary.KeyEnum.MESSAGE_EMPTY,
                    Environment.StackTrace, er));
            }
        }
        private void WriteCompressedFile(FileStream fStream, ZipEntry entry)
        {
            MemoryStream ms = new MemoryStream();
            try
            {
                if (method == ZipConstants.DEFLATE)
                    zipStream = new DeflateStream(ms, CompressionMode.Compress,
                        true);
                else if (method == ZipConstants.GZIP)
                    zipStream = new GZipStream(ms, CompressionMode.Compress,
                        true);

                byte[] buffer = new byte[fStream.Length];
                fStream.Read(buffer, 0, buffer.Length);
                zipStream.Write(buffer, 0, buffer.Length);
                zipStream.Close();

                byte[] b = new byte[ms.Length];
                ms.Seek(0, SeekOrigin.Begin);
                ms.Read(b, 0, b.Length);
                baseStream.Write(b, 0, b.Length);
                //Go back and write the length and the CRC
                WriteCompressedSizeCRC((int)ms.Length, ComputeMD5(b), entry);

            }
            catch (Exception er)
            {
                Dialog.Show(Dialog_type.Error, SR.StringLibrary.GetStrMSG(SR.StringLibrary.KeyEnum.MESSAGE_EMPTY,
                    Environment.StackTrace, er));
            }
            finally
            {
                ms.Close();
            }

        }
        private void WriteCompressedSizeCRC(Int32 value, byte[] crc, ZipEntry entry)
        {
            try
            {
                entry.CompressedSize = value;
                entry.SetCrc(crc);
                baseStream.Seek(offset, SeekOrigin.Begin);
                WriteLeInt32(entry.CompressedSize);
                WriteBytes(crc);
                //Remove the recorded offset
                offset = -1;
                baseStream.Seek(0, SeekOrigin.End);
            }
            catch (Exception er)
            {
                Dialog.Show(Dialog_type.Error, SR.StringLibrary.GetStrMSG(SR.StringLibrary.KeyEnum.MESSAGE_EMPTY,
                    Environment.StackTrace, er));
            }
        }
        private byte[] ConvertToArray(string name)
        {
            return System.Text.Encoding.ASCII.GetBytes(name);
        }
        public void CloseHeaders(Int16 numberOfFiles)
        {
            WriteSuperHeader(numberOfFiles);
        }
        public void Add(ZipEntry entry)
        {
            FileStream fs = null;
            try
            {
                fs = File.OpenRead(entry.Name);
                entry.Size = (Int32)fs.Length;
                entry.DateTime = File.GetLastWriteTime(entry.Name);
                PutNextHeader(entry);
                WriteCompressedFile(fs, entry);
                //CompressionForm.statusMessage = ZipConstants.AddMessage;
            }
            catch (Exception er)
            {
                Dialog.Show(Dialog_type.Error, SR.StringLibrary.GetStrMSG(SR.StringLibrary.KeyEnum.MESSAGE_EMPTY,
                    Environment.StackTrace, er));
            }
            finally
            {
                if (fs != null)
                    fs.Close();
            }
        }
        public byte Method
        {
            get
            {
                return method;
            }
            set
            {
                method = value;
            }
        }
        public byte[] ComputeMD5(byte[] input)
        {
            return md5.ComputeHash(input);
        }
    }
    public class ZipEntry
    {
        private Int32 size;
        private Int32 compressedSize;
        private Int32 dosTime;			//Time represented as an Int
        private Int16 nameLength;		//Length of the variable sized name
        private byte[] crc;             //Array of 16 CRC bytes
        private string name;
        public ZipEntry(string name)
        {
            if (name == null)
            {
                //Wrong entry
                throw new System.ArgumentNullException();
            }
            this.DateTime = System.DateTime.Now;
            this.Name = name;
            this.size = 0;
            this.compressedSize = 0;
            this.crc = new byte[16];
        }
        public Int32 DosTime
        {
            get
            {
                return dosTime;
            }
            set
            {
                this.dosTime = value;
            }
        }
        public Int16 NameLength
        {
            get
            {
                return nameLength;
            }
            set
            {
                //Check if the value is greater than 16 bytes
                if ((UInt16)value > 0xffff)
                    throw new ArgumentOutOfRangeException();
                this.nameLength = value;
            }
        }
        public DateTime DateTime
        {
            get
            {
                int sec = 2 * (dosTime & 0x1f);
                int min = (dosTime >> 5) & 0x3f;
                int hrs = (dosTime >> 11) & 0x1f;
                int day = (dosTime >> 16) & 0x1f;
                int mon = ((dosTime >> 21) & 0xf);
                int year = ((dosTime >> 25) & 0x7f) + 1980; /* since 1900 */
                return new System.DateTime(year, mon, day, hrs, min, sec);
            }
            set
            {
                DosTime = ((Int32)value.Year - 1980 & 0x7f) << 25 |
                          ((Int32)value.Month) << 21 |
                          ((Int32)value.Day) << 16 |
                          ((Int32)value.Hour) << 11 |
                          ((Int32)value.Minute) << 5 |
                          ((Int32)value.Second) >> 1;
            }
        }
        public string Name
        {
            get
            {
                return name;
            }
            set
            {
                //Check if the value is greater than 16 bytes or null
                if (value == null || value.Length > 0xffffL)
                    throw new ArgumentOutOfRangeException();

                if (value.Length != 0)
                {
                    name = value;
                    nameLength = (Int16)value.Length;
                }
            }
        }
        public Int32 Size
        {
            get
            {
                return size;
            }
            set
            {
                //Check if the value is greater than 32 bytes
                if ((UInt32)value > 0xffffffffL)
                    throw new ArgumentOutOfRangeException();

                this.size = (Int32)value;
            }
        }
        public Int32 CompressedSize
        {
            get
            {
                return compressedSize;
            }
            set
            {
                //Check if the value is greater than 32 bytes
                if ((UInt32)value > 0xffffffffUL)
                    throw new ArgumentOutOfRangeException();

                this.compressedSize = (Int32)value;

            }
        }
        public byte[] GetCrc()
        {
            return crc;
        }
        public void SetCrc(byte[] value)
        {
            //Check if the Length of value array is greater than 16
            if (value.Length != crc.Length)
                throw new ArgumentOutOfRangeException();

            crc = value;
        }
    }
    sealed class ZipConstants
    {
        public ZipConstants()
        {
        }
        public const byte DEFLATE = 4;	//Indicating deflate method of compression
        public const byte GZIP = 6;		//Indicating gzip method of compression
        public const int FixedHeaderSize = 30; //Fixed part of the header
        public const int SuperHeaderSize = 3;
        public const string AddFiles = "Add files";
        public const string NewArchive = "New archive";
        public const string OpenArchive = "Open archive";
        //Extensions for the files
        public const string Extension = "xip files (*.xip)|*.xip";
        public const string AllExtensions = "All files (*.*)|*.*";

        public const string BackSlash = "\\";
        public const string Dot = ".";

        public const string ExtractMessage =
                                "Extracted files to {0} successfully.";
        public const string OpenMessage = "Opened archive {0} for editing";
        public const string NewMessage = "Opened new archive {0} for editing";
        public const string AddMessage = "Added files to the archive.";

        public const string Title = "Compression sample";
        public const string GzipName = "GZip";
        public const string DeflateName = "Deflate";
        public const string ErrorName = "Error";

        public const string SeekError = "Attempted to seek before the beginning";
        public const string IOError = "An IO error has occured";
        public const string CloseError = "Stream already closed";
        public const string MemoryError = "Running out of memory";
        public const string ArgumentError = "Argument invalid";
        public const string FileNotFoundError = "File not found";
        public const string FileError = "Filename invalid";
        public const string CorruptedError =
                "Trying to read a corrupted xip file";
        public const string FileExistsError = "File already exists";

        public const string FileReplace =
            "The file chosen already exists. Do you want to replace it?";
        public const string Replace = "Replace";
        //public static void ShowError(string error)
        //{
        //    MessageBoxOptions opt;
        //    if (System.Threading.Thread.CurrentThread.
        //        CurrentUICulture.TextInfo.IsRightToLeft == true)
        //        opt = MessageBoxOptions.RightAlign |
        //            MessageBoxOptions.RtlReading;
        //    else
        //        opt = MessageBoxOptions.DefaultDesktopOnly;
        //}
    }
}
